using Microsoft.Extensions.Logging;
using Sage.Http.Options;
using System.Diagnostics;

namespace Sage.Http.Handlers
{
    /// <summary>
    /// 实现HTTP请求重试策略的消息处理器
    /// </summary>
    /// <remarks>
    /// 初始化重试处理器
    /// </remarks>
    /// <param name="options">重试策略配置</param>
    /// <param name="logger">日志记录器</param>
    public class RetryHandler(RetryOptions options, ILogger logger) : DelegatingHandler
    {
        private readonly RetryOptions _options = options ?? throw new ArgumentNullException(nameof(options));
        private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger));

        /// <summary>
        /// 拦截请求并实现重试逻辑
        /// </summary>
        /// <param name="request">HTTP请求消息</param>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns>HTTP响应消息</returns>
        protected override async Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request,
            CancellationToken cancellationToken)
        {
            if (!_options.Enabled)
            {
                return await base.SendAsync(request, cancellationToken);
            }

            // 在开始前检查取消令牌
            cancellationToken.ThrowIfCancellationRequested();

            int retryCount = 0;
            HttpResponseMessage? response = null;
            List<Exception> exceptions = [];

            // 方法、URL等非幂等操作不重试
            bool canRetry = IsRetryable(request);
            if (!canRetry)
            {
                return await base.SendAsync(request, cancellationToken);
            }

            while (retryCount <= _options.MaxRetryCount)
            {
                // 在每次重试前检查取消令牌
                cancellationToken.ThrowIfCancellationRequested();

                try
                {
                    // 需要为每次重试创建新的请求副本，因为HttpRequestMessage不可重用
                    var requestCopy = retryCount > 0 ? await CloneHttpRequestMessageAsync(request, cancellationToken) : request;

                    // 发送请求
                    response = await base.SendAsync(requestCopy, cancellationToken);

                    // 检查响应状态是否需要重试
                    if (!ShouldRetry(response))
                    {
                        return response;
                    }

                    _logger.LogWarning(
                        "请求状态码 {StatusCode} 需要重试，当前重试次数: {RetryCount}/{MaxRetryCount}",
                        (int)response.StatusCode, retryCount, _options.MaxRetryCount);
                }
                catch (TimeoutException ex)
                {
                    // 超时：不重试，直接停止
                    _logger.LogWarning(ex, "请求超时，停止重试");
                    throw;
                }
                catch (Exception ex) when (IsCancellationException(ex, cancellationToken))
                {
                    // 如果是取消相关的异常，直接抛出，不进行重试
                    _logger.LogInformation("请求被取消，停止重试: {Message}", ex.Message);
                    throw;
                }
                catch (Exception ex) when (ex is HttpRequestException || ex is TaskCanceledException)
                {
                    exceptions.Add(ex);
                    _logger.LogWarning(ex,
                        "请求发生异常，当前重试次数: {RetryCount}/{MaxRetryCount}, 错误: {Message}",
                        retryCount, _options.MaxRetryCount, ex.Message);
                }

                // 最后一次尝试失败
                if (retryCount == _options.MaxRetryCount)
                {
                    break;
                }

                // 计算下一次重试的延迟时间（指数退避策略）
                var delayMs = CalculateDelayMs(retryCount);
                retryCount++;

                _logger.LogInformation("将在 {DelayMs}ms 后进行第 {RetryCount} 次重试", delayMs, retryCount);

                // 延迟指定时间后重试
                try
                {
                    await Task.Delay(delayMs, cancellationToken);
                }
                catch (TaskCanceledException)
                {
                    // 取消令牌被触发
                    _logger.LogInformation("重试延迟期间请求被取消");
                    throw;
                }
            }

            // 所有重试都失败，返回最后一个响应或抛出聚合异常
            if (response != null)
            {
                return response;
            }

            if (exceptions.Count != 0)
            {
                throw new AggregateException($"在 {_options.MaxRetryCount} 次尝试后请求仍然失败", exceptions);
            }

            // 不应该达到这里，但为了代码完整性添加
            throw new HttpRequestException("请求失败，无法获取有效响应");
        }

        /// <summary>
        /// 判断异常是否为取消相关的异常
        /// </summary>
        /// <param name="exception">异常对象</param>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns>是否为取消相关的异常</returns>
        private static bool IsCancellationException(Exception exception, CancellationToken cancellationToken)
        {
            // 检查是否为OperationCanceledException或其派生类
            if (exception is OperationCanceledException operationCanceledException)
            {
                // 检查是否是由我们的取消令牌触发的
                return operationCanceledException.CancellationToken == cancellationToken ||
                       cancellationToken.IsCancellationRequested;
            }

            // 检查TaskCanceledException（它是OperationCanceledException的派生类）
            if (exception is TaskCanceledException taskCanceledException)
            {
                // 如果取消令牌已被请求，则认为是取消异常
                if (cancellationToken.IsCancellationRequested)
                {
                    return true;
                }

                // 检查TaskCanceledException的CancellationToken
                return taskCanceledException.CancellationToken == cancellationToken;
            }

            // 检查SocketException中的特定错误码（995: 由于线程退出或应用程序请求，已中止 I/O 操作）
            if (exception is System.Net.Sockets.SocketException socketException)
            {
                // 错误码995通常表示操作被取消
                if (socketException.ErrorCode == 995 && cancellationToken.IsCancellationRequested)
                {
                    return true;
                }
            }

            // 检查HttpRequestException的内部异常
            if (exception is HttpRequestException httpRequestException && httpRequestException.InnerException != null)
            {
                return IsCancellationException(httpRequestException.InnerException, cancellationToken);
            }

            return false;
        }

        /// <summary>
        /// 判断请求是否可以重试
        /// </summary>
        /// <param name="request">HTTP请求消息</param>
        /// <returns>是否可以重试</returns>
        private static bool IsRetryable(HttpRequestMessage request)
        {
            // GET、HEAD、OPTIONS、TRACE 方法通常是幂等的，可以安全重试
            // PUT 和 DELETE 通常也是幂等的
            // POST 通常不是幂等的，但可以通过自定义头标记为可重试

            if (request.Method == HttpMethod.Get ||
                request.Method == HttpMethod.Head ||
                request.Method == HttpMethod.Options ||
                request.Method == HttpMethod.Trace ||
                request.Method == HttpMethod.Put ||
                request.Method == HttpMethod.Delete)
            {
                return true;
            }

            // 检查自定义头，允许标记POST为可重试
            if (request.Headers.Contains("X-Retry-Allowed"))
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// 判断响应状态是否需要重试
        /// </summary>
        /// <param name="response">HTTP响应消息</param>
        /// <returns>是否需要重试</returns>
        private bool ShouldRetry(HttpResponseMessage response)
        {
            int statusCode = (int)response.StatusCode;
            return _options.RetryStatusCodes.Contains(statusCode);
        }

        /// <summary>
        /// 计算重试延迟时间（使用指数退避策略）
        /// </summary>
        /// <param name="retryCount">当前重试次数</param>
        /// <returns>延迟毫秒数</returns>
        private int CalculateDelayMs(int retryCount)
        {
            // 使用指数退避策略：初始延迟 * (增长因子 ^ 重试次数)
            double delay = _options.InitialDelayMs * Math.Pow(_options.BackoffMultiplier, retryCount);

            // 应用抖动防止雪崩（添加随机变化）
            Random jitter = new();
            delay *= 0.8 + jitter.NextDouble() * 0.4; // 80% - 120% 的原始延迟

            // 确保不超过最大延迟时间
            return (int)Math.Min(delay, _options.MaxDelayMs);
        }

        /// <summary>
        /// 克隆HTTP请求消息（因为HttpRequestMessage不可重用）
        /// </summary>
        /// <param name="request">原始请求消息</param>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns>克隆的请求消息</returns>
        private static async Task<HttpRequestMessage> CloneHttpRequestMessageAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
        {
            // 在开始克隆前检查取消令牌
            cancellationToken.ThrowIfCancellationRequested();

            var clone = new HttpRequestMessage(request.Method, request.RequestUri);

            // 复制请求头
            foreach (var header in request.Headers)
            {
                clone.Headers.TryAddWithoutValidation(header.Key, header.Value);
            }

            try
            {
                // 尝试复制所有选项
                foreach (var option in request.Options)
                {
                    clone.Options.Set(new HttpRequestOptionsKey<object?>(option.Key), option.Value);
                }
            }
            catch (Exception ex)
            {
                // 记录异常并退回到手动复制已知选项
                Debug.WriteLine($"复制选项失败: {ex.Message}");

                // 退回到手动复制重要选项
                if (request.Options.TryGetValue(new HttpRequestOptionsKey<TimeSpan>("RequestTimeout"), out var timeout))
                {
                    clone.Options.Set(new HttpRequestOptionsKey<TimeSpan>("RequestTimeout"), timeout);
                }
                // 添加其他已知重要选项...
            }

            // 复制版本
            clone.Version = request.Version;

            // 复制内容（如果有）
            if (request.Content != null)
            {
                // 在读取内容前检查取消令牌
                cancellationToken.ThrowIfCancellationRequested();

                // 读取原始内容
                var content = await request.Content.ReadAsStringAsync(cancellationToken);

                // 创建新的StringContent并复制原始请求的内容类型
                var contentType = request.Content.Headers.ContentType;
                var newContent = new StringContent(content);

                if (contentType != null && contentType.MediaType != null)
                {
                    newContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentType.MediaType);
                }

                // 复制其他内容头
                foreach (var header in request.Content.Headers)
                {
                    if (header.Key != "Content-Type") // 已经处理过内容类型
                    {
                        newContent.Headers.TryAddWithoutValidation(header.Key, header.Value);
                    }
                }

                clone.Content = newContent;
            }

            return clone;
        }
    }
}
